#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import sys
import os
import re
import tempfile
from xml.etree.ElementTree import Element,ElementTree

r'''
问题:
阅读Linux内核代码时发现
1. 不知道自己编译的内核使用了哪些文件。
2. 不知道编译的时候使用了哪些宏定义。

解决解决办法:
1. 
内核编译时生成了.%.cmd。比如生成init/main.o时会有一个.main.o.cmd文件记录了生成这个.o的依赖文件。
和生成命令。

cmd_init/main.o := powerpc64-fsl-linux-gcc -mbig-endian -m64 -Wp,-MD,init/.main.o.d -nostdinc -isystem /opt/fsl-qoriq/2.0/sysroots/x86_64-fslsdk-linux/usr/lib/powerpc64-fsl-linux/gcc/powerpc64-fsl-linux/4.9.2/include -I../arch/powerpc/include -Iarch/powerpc/include/generated/uapi -Iarch/powerpc/include/generated  -I../include -Iinclude -I../arch/powerpc/include/uapi -Iarch/powerpc/include/generated/uapi -I../include/uapi -Iinclude/generated/uapi -include ../include/linux/kconfig.h  -I../init -Iinit -D__KERNEL__  -I../arch/powerpc -Iarch/powerpc -DHAVE_AS_ATHIGH=1 -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-PIE -std=gnu89 -msoft-float -pipe  -I../arch/powerpc -Iarch/powerpc -mtraceback=no -mcall-aixdesc -mcmodel=medium -mno-pointers-to-nested-functions -mcpu=powerpc64 -mno-altivec -mno-vsx -mno-spe -mspe=no -funit-at-a-time -fno-dwarf2-cfi-asm -mno-string -Wa,-maltivec -fno-delete-null-pointer-checks -Os -Wno-maybe-uninitialized --param=allow-store-data-races=0 -Wframe-larger-than=2048 -fno-stack-protector -Wno-unused-but-set-variable -fomit-frame-pointer -fno-var-tracking-assignments -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DCC_HAVE_ASM_GOTO    -D"KBUILD_STR(s)=\#s" -D"KBUILD_BASENAME=KBUILD_STR(main)"  -D"KBUILD_MODNAME=KBUILD_STR(main)" -c -o init/.tmp_main.o ../init/main.c

source_init/main.o := ../init/main.c

deps_init/main.o := \
  ../include/linux/kconfig.h \
    $(wildcard include/config/cpu/big/endian.h) \
    $(wildcard include/config/booger.h) \
    $(wildcard include/config/foo.h) \
  ../include/linux/compiler_types.h \
  /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/lib/gcc/aarch64-linux-gnu/7.5.0/include/stdarg.h \
  /home/sjl/work/buildbot/ygw/tmp/linux-4.14.10_20241218/include/linux/linkage.h \
  /home/sjl/work/buildbot/ygw/tmp/linux-4.14.10_20241218/include/linux/stringify.h \
  /home/sjl/work/buildbot/ygw/tmp/linux-4.14.10_20241218/include/linux/export.h \
    $(wildcard include/config/have/underscore/symbol/prefix.h) \
    $(wildcard include/config/modules.h) \
    $(wildcard include/config/modversions.h) \
    $(wildcard include/config/module/rel/crcs.h) \
    $(wildcard include/config/trim/unused/ksyms.h) \
    $(wildcard include/config/unused/symbols.h) \
  /home/sjl/work/buildbot/ygw/tmp/linux-4.14.10_20241218/arch/arm64/include/asm/linkage.h \
  ....
    $(wildcard include/config/firewire/net.h) \
  /home/sjl/work/buildbot/ygw/tmp/linux-4.14.10_20241218/include/uapi/linux/if_arp.h \
  include/linux/mmdebug.h \
    $(wildcard include/config/debug/vm.h) \
    $(wildcard include/config/debug/vm/pgflags.h) \


init/main.o: $(deps_init/main.o)

$(deps_init/main.o):

通过分析
source_$$ :=
deps_$$ :=
里面包含的内容可以知道编译时都使用了哪些文件。

2. 内核宏定义的确定

可以将cmd_init/main.o 命令修改成

$ echo |  powerpc64-fsl-linux-gcc -mbig-endian -m64 -nostdinc -isystem ... -E -dMM -x c -

删除-Wp,-MD,init/.main.o.d 
将 -c -o 后续的字符替换成-E -dMM -x c -

这样就可以生成编译内核的大多数宏定义.
原理其实就是通过gcc -E -dMM 将gcc内部的宏定义
通过include/linux/kconfig.h 导入include/generated/autoconf.h里面的宏定义的值全部输出。


本程序通过分析 .*.o.cmd文件的内容。得到内核编译需要的所有源文件和内核宏定义开关。
将

'''

# create CONFIG_XXXX 0
def create_condition_xml(xml, truedef, conf):
    root = Element('SourceInsightParseConditions')
    tree = ElementTree(root)
    root.attrib['AppVer']='4.00.0096'
    root.attrib['AppVerMinReader']='4.00.0019'
    child = Element('ParseConditions')
    root.append(child)
    defines = Element('Defines')
    child.append(defines)

    ok = set()
    for one in open(truedef):
        # 非贪婪匹配
        m = re.match('#define (.*?) ', one)
        if m == None: continue
        m1 = m.group(1)
        if m1 in ok: continue
        if m1 in conf: conf.discard(m1)
        ok.add(m1)
        define=Element('define')
        define.attrib['id']=m1
        define.attrib['value']='1'
        defines.append(define)

    for one in sorted(conf): 
        define=Element('define')
        define.attrib['id']=one
        define.attrib['value']='0'
        defines.append(define)
    tree.write(xml, encoding='UTF-8', xml_declaration=True)
    return xml

def find_config_xxx(fn, conf):
    try:
        with open(fn, 'r') as fn: 
            text=fn.read()
            for one in re.finditer(r'[ (](CONFIG_[\da-zA-Z_]+)', text):
                cnf=one.group(1)
                #cnf=re.sub('[()\s]', '', cnf)
                conf.add(cnf)
    except:return

def create_autoconf_h(doth):
    cmd = None
    with open('init/.main.o.cmd', 'r') as fn: cmd = fn.read()
    if cmd == None: return
    #m = re.match(r'cmd_.*:= (.*?) -D"?KBUILD_', cmd)
    #if not m:
    #    m = re.match(r'savedcmd_.*:= (.*?) -D"?KBUILD_', cmd)
    m = re.match(r'.*?cmd_.*:= (.*?) -D"?KBUILD_', cmd)
    if m == None: return

    # gcc -Wp,-MMD,init/.main.o.d -nostdinc 
    cmd = re.sub(' -Wp,.*? ', ' ', m.group(1))
    cmd = f'echo | {cmd} -E -dMM -x c - -o {doth}'
    os.system(cmd)

def process_dotcmd(fn, gz, src, xxx):
    with open(fn, 'r') as fn: text = fn.read()
    #get source first
    # source_init/main.o := init/main.c
    m = re.search(r'source_(.*) := (.*)', text)
    if m == None: return 
    o=m.group(1) # source_init/main.o
    c=os.path.normpath(m.group(2)) # init/main.c
    if c not in src:
        src.add(c)
        if os.path.isabs(c): c=os.path.relpath(c)
        if os.path.exists(c) and os.path.getsize(c)>0:
            find_config_xxx(c, xxx)
            print(c, file=gz)
    #get dep files
    m = re.search(r'deps_.* := \\\n(.*)\\\n', text, re.M|re.S)
    if m == None: return
    text = m.group(1)
    text = re.split(r'\n', text);
    for line in text:
        m = re.match(r'.*\s(.*?)[\s)]', line)
        if m == None: continue
        c = os.path.normpath(m.group(1))
        if c in src: continue
        src.add(c)
        if (not os.path.exists(c)) or os.path.getsize(c) == 0: continue
        ##print(c) 
        if os.path.isabs(c): c=os.path.relpath(c)
        find_config_xxx(c, xxx)
        print(c, file=gz)

def usage():
    print(r'''usage: build_kernel_src.py <objtree> {save.tar.gz}
    <objtree> 是内核的编译目录就是make O=objtree O指定的目录，如果没有指定就是源代码目录。 
    {save.tar.gz} 是生成的压缩包。''')
    sys.exit(-1)

if __name__ ==  "__main__":
    if(len(sys.argv)<2 or (not os.path.isdir(sys.argv[1]))):
        usage()
    objdir=os.path.realpath(f'{sys.argv[1]}')
    objdir=os.path.normpath(objdir)
    srcdir=os.path.join(objdir, "source")
    if os.path.exists(srcdir) and os.path.islink(srcdir):
        srcdir=os.path.realpath(srcdir)
        srcdir=os.path.normpath(srcdir)
    else:
        srcdir=objdir

    #srcdir=os.path.realpath(f'{sys.argv[1]}/source')
    #srcdir=os.path.normpath(srcdir)
    if len(sys.argv)>2: gz=os.path.realpath(sys.argv[2])
    else: gz=os.path.basename(f"{objdir}.tar.gz")
    
    #print(f"{srcdir} {objdir}");exit(0)
    xml = f'{os.path.basename(gz)}.conditions.xml'
    #gz = os.popen(f'tar -C {objdir} -cvzf {gz} -T- 2>tar.log 1>&2', mode='w')
    gz = os.popen(f'tar -C {objdir} -czf {gz} -T- 2>/dev/null 1>&2', mode='w')
    os.chdir(objdir)
    # save .config first
    print('./.config', file=gz)
    print('./System.map', file=gz)
    doth='./autoconf.h'
    create_autoconf_h(doth)
    print(doth, file=gz)

    find = 'find . -type f -name ".*.o.cmd"'
    find = f'{find} -and -not -name ".*.mod.o.cmd"'
    find = f'{find} -and -not -name ".*.built-in.o.cmd"'
    find = f'{find} -and -not -path "./tools/*"'
    find = f'{find} -and -not -path "./scripts/*"'
    find = f'{find} -and -not -path "./usr/*"'
 
    src = set()
    config_xxx = set()
    ll = 0
    for fn in os.popen(find):
        fn = fn.strip()
        process_dotcmd(fn, gz, src, config_xxx)
        print(f'\r{len(src)}+%-*s'%(ll,fn), file=sys.stderr, end='', flush=True)
        ll = len(fn)
    print(f'\nwaiting {len(src)} files')
    create_condition_xml(xml, doth, config_xxx)
    print(xml, file=gz)
    gz.close()
    os.unlink(doth)
    os.unlink(xml)
